/*  
 * Copyright (c) 2010, James Daniello
 * This project is licensed under the 'New BSD' license.
 * The full text of the license can be found in the root
 * directory under the filename "LICENSE"
 */
package src.network;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.io.File;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Vector;

/**
 * The PICAIUServer is the main server program for connecting to and interacting with clients. Running
 * program begins a server for PICAIU and allows users to interact with each other and play games.
 */
public class PICAIUServer extends Thread {

  public static final int PORT = 8555;
  public static boolean VERBOSE = true; /* DEBUGGING. Output data to screen.*/
  public static int MAXUNITS = 24;
  public static float UNITRETURNRATE=.7f;

  //data used for connecting to the database. Data is retreived from a configuration file
  public static String database = "";
  public static String dbUser = "";
  public static String dbPass = "";
  //'running' indicates if the program is still running and should loop
  private boolean running = true;
  //a list of all the clients connected to the server
  private List<PICAIUServerPersona> clients;
  private String authPass;
  public static String MYHOST;
  //connection pool of connections to the database
  private int dbCount = 20;
  private int dbCountMax = 35;
  private int dbActual = 0;
  private Vector<PICAIUDBConnection> dbConnPool;
  //the game types and amounts their worth
  ArrayList<String> gameTypes = new ArrayList<String>();
  //list of games being played
  private ArrayList<PICAIUGame> gamesBeingPlayed = new ArrayList<PICAIUGame>();
  private java.util.concurrent.ConcurrentHashMap<String, PICAIUGame> gamesBeingPlayedRef;

  /**
   * Constructor for the PICAIUServer. A list of users is created and the database information is
   * retreived here.
   */
  public PICAIUServer() {
    clients = new ArrayList<PICAIUServerPersona>();
    gamesBeingPlayedRef = new java.util.concurrent.ConcurrentHashMap<String, PICAIUGame>();
    readConfigFile();
    initGameTypes();
    createDBPool(dbCount);
    cleanUpDB();
    setUpTempPass();
    System.out.println("Server up and running with (" + dbActual + ") MYSql connections in pool.");
    PICAIUAIScripts.init();
  }

  public void createDBPool(int count) {
    dbConnPool = new Vector<PICAIUDBConnection>(count);
    for (int i = 0; i < count; i++) {
      PICAIUDBConnection newConn = new PICAIUDBConnection();
      if (newConn.isConnected()) {
        dbActual++;
        dbConnPool.add(newConn);
      }
    }
  }

  //Sets the database up again for play. Includes putting all soldiers back in pool
  private void cleanUpDB() {
    PICAIUDBConnection newConn = new PICAIUDBConnection();
    newConn.cleanUp();
    newConn = null;
  }

  /**
   * sets up a temporary password used for various things including authenticating that a request to the
   * php emailer comes from this server
   */
  private void setUpTempPass() {
    try {
      int i = 32 + (int) (Math.random() * 10);
      String pass = "";
      for (int j = 0; j < i; j++) {
        char x = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".charAt((int) (Math.random() * 62));
        pass += x;
      }

      File passOut = new File("pass");
      if (!passOut.exists()) {
        passOut.createNewFile();
      }
      PrintWriter passPO = new PrintWriter(passOut);
      passPO.print(pass);
      passPO.flush();
      passPO.close();
      authPass = pass;
    } catch (Exception e) {
      System.out.println("Could not create a new passfile 'pass'. Exiting for security reasons");
      System.out.println(e);
      System.exit(-1);
    }
  }

  /**
   * Returns the authpass
   */
  public String getAuthPass() {
    return authPass;
  }

  /**
   * Opens up the database configuration file and stores the data needed to access the MySQL database.
   */
  private void readConfigFile() {
    try {
      Scanner dbScanner = new Scanner(new File("serverconfig.txt"));
      String next;
      while (dbScanner.hasNext()) {
        next = dbScanner.nextLine();
        if (!next.startsWith("#") && !next.startsWith("</")) {

          //get name of database within mysql
          if (next.equals("<db>")) {
            database = dbScanner.next();
          }
          //name of user used to access database
          if (next.equals("<dbuser>")) {
            dbUser = dbScanner.next();
          }
          //password of user used to access database
          if (next.equals("<dbpass>")) {
            dbPass = dbScanner.next();
          }
          //host of server
          if (next.equals("<host>")) {
            MYHOST = dbScanner.next();
          }
          //password of user used to access database
          if (next.equals("<dbconnectcount>")) {
            dbCount = dbScanner.nextInt();
          }
          //password of user used to access database
          if (next.equals("<dbconnectcountmax>")) {
            dbCountMax = dbScanner.nextInt();
          }

          if (next.equals("<verbose>")) {
            try {
              VERBOSE = dbScanner.nextBoolean();
            } catch (Exception e) {
              VERBOSE = false;
            }
          }

        }
      }
      dbScanner.close();
    } catch (Exception e) {
      System.out.println(e);
      System.out.println("Can not locate dbconnection.cfg file");
      System.exit(0);
    }
  }

  /**
   * This program is the main class for the server. It starts a server and prepares it to accept incoming
   * connections.
   */
  public static void main(String[] args) {

    PICAIUServer runningServer = new PICAIUServer();
    Thread t = new Thread(runningServer);
    t.start();

  }

  /**
   * While the server is running it loops to check for incoming connections and creates them
   * as necessary.
   */
  public void run() {
    //the new possible connection
    PICAIUServerPersona temp = null;
    try {
      //open socket for connections.
      ServerSocket serverSocket = new ServerSocket(PORT);

      while (running) {
        //Check for new connections
        Socket client = serverSocket.accept();
        if (PICAIUServer.VERBOSE) {
          System.out.println("Accepted client:");
        }
        //the new connection
        temp = new PICAIUServerPersona(client, this);

        //sleep to allow other threads to run. This code may be removed in the future
        sleep(1);
      }

      //error setting up server socket
    } catch (Exception e) {
      //if any errors happen clear client out of system.
      clearPersona(temp);
      System.out.println(e);
      System.out.println("Failed to start connection on port: " + PORT + ". Shutting down server");
      System.exit(1);
    }
  }

  /**
   * Clears the given PICAIUServerPersona out of the system. Tells all clients that this person has
   * been disconnected. Then removes it from the server's list of clients and nulls it out.
   * @param persona the PICAIUServerPersona to be removed from the server.
   */
  protected void clearPersona(PICAIUServerPersona persona) {
    if (persona.getUserName() != null && persona.isConnected()) {
      sendDataToAll("RMVUSR#" + persona.getUserName());
    }

    if (clients.contains(persona)) {
      clients.remove(persona);
    }
    persona.clear();
    persona = null;
  }

  /**
   *  Adds a persona to the system. It is likely this will only be called upon creation of a new connection
   * and associated PICAIUServerPersona, however there may be more uses for it later.
   * @param persona the person to add to the list of connections
   */
  public void addPersona(PICAIUServerPersona persona) {
    clients.add(persona);
  }

  /**
   * Sends a string message to all clients and appends the text message control character to the
   * beginning of the TCP message.
   * @param message the message to be sent to all users
   */
  protected void sendLobbyMessageToAll(String message) {
    for (PICAIUServerPersona persona : clients) {
      persona.sendMessageToClient(message);
    }
  }

  /**
   * Sends a data message to all clients and appends the data message control character to the
   * beginning of the TCP message.
   * @param message the message to be sent to all users
   */
  protected void sendDataToAll(String data) {
    for (PICAIUServerPersona persona : clients) {
      persona.sendDataToClient(data);
    }
  }

  /**
   * Clears the user out of the system, but also notifies all users that this user has disconnected
   * @param message the message to be sent to all users
   */
  protected void disconnectClient(PICAIUServerPersona persona) {
    persona.returnUnits();
    clearStartedGames(persona);

    //clear from system
    clearPersona(persona);
    if (persona.getUserName() != null) {
      sendDataToAll("RMVUSR#" + persona.getUserName());
      sendLobbyMessageToAll(persona.getUserName() + " has Disconnected from the Server");
    }

  }

  /**
   * This method will search for all personas with the same username as the personaIn variable
   * specified that aren't actually the persona specified and remove them.
   * @param personaIn the persona with which to remove duplicates of
   */
  protected void clearDuplicates(PICAIUServerPersona personaIn) {

    if (PICAIUServer.VERBOSE) {
      System.out.println("clearing out duplicates " + personaIn.getUserName());
    }

    //Checks all the personas with the same name as the one specified.
    //Removes any with the same name which are not actually the persona passed in.
    for (PICAIUServerPersona persona : clients) {
      if (personaIn.getUserName().equals(persona.getUserName()) && persona != personaIn) {
        persona.sendMessageToClient("#SERVER: Someone else has logged on with your username. "
                + "If it was not you, please reset your password on the log-in screen.");
        persona.disconnect();
        clients.remove(persona);
        persona.clear();
        persona = null;
      }
    }


  }

  /**
   * This returns a preformatted string containing the list of all users connected so that a client
   * knows how to parse it and add the users to their lobby list.
   */
  protected String getUserList() {
    String message = "USRLST";
    for (PICAIUServerPersona persona : clients) {
      if (persona.getUserName() != null && !persona.isInGame()) {
        message += "#" + persona.getUserName();
      }
    }
    return message;
  }

  /**
   * This returns a preformatted string containing the list of all existing games so that a client
   * knows how to parse it and add the games to their lobby list.
   */
  protected String getGamesList() {
    String message = "GMSLST";
    for (PICAIUGame g : gamesBeingPlayed) {
      if (g != null && g.hasOnePlayer()) {
        message += "#" + g.getFormattedGameName();
      }
    }
    return message;
  }

  protected synchronized void putBackDBConn(PICAIUDBConnection conn) {
    dbConnPool.add(conn);
  }

  protected synchronized PICAIUDBConnection grabDBConn(PICAIUServerPersona persona) {
    int tries = 5;
    while (tries-- > 0) {
      if (!dbConnPool.isEmpty()) {
        return dbConnPool.remove(dbConnPool.size() - 1);
      } else {
        if (dbCount < dbCountMax) {
          PICAIUDBConnection newConn = new PICAIUDBConnection();
          if (newConn.isConnected()) {
            dbCount++;
            dbActual++;
            return newConn;
          }
          return null;
        }
        long time = System.currentTimeMillis();
        System.out.println("beginning wait");
        while (System.currentTimeMillis() - time < 2000) {
        }
        System.out.println("end wait");
      }
    }
    persona.sendDataToClient("ERR#DB2");
    persona.disconnect();
    return null;
  }

  /**
   * Sets the available game types.
   */
  private void initGameTypes() {
    gameTypes.add("100-500");
    gameTypes.add("500-2,000");
    gameTypes.add("2,000-5,000");
    gameTypes.add("5,000-10,000");
    gameTypes.add("10,000-50,000");
    gameTypes.add("50,000-100,000");
  }

  String getGameTypes() {
    String types = "";
    for (String s : gameTypes) {
      types += s + "#";
    }
    return types.substring(0, types.length() - 1);
  }

  protected PICAIUGame addGame(PICAIUServerPersona persona, String amount, String password) {
    for (PICAIUGame g : gamesBeingPlayed) {
      if (g.getRedPersona() == persona
              || g.getBluePersona() == persona) {
        return null;
      }
    }
    if (VERBOSE) {
      System.out.println("*** Creating new game [" + persona.getUserName() + ">>" + amount + "] ***");
    }
    PICAIUGame g = new PICAIUGame(persona, amount, password, this);
    gamesBeingPlayed.add(g);
    gamesBeingPlayedRef.put(g.getFormattedGameName(), g);
    return g;
  }

  protected PICAIUGame getGame(String name) {
    return gamesBeingPlayedRef.get(name);
  }

  protected void changeGameName(String oldName, String newName) {
    PICAIUGame g = gamesBeingPlayedRef.get(oldName);
    gamesBeingPlayedRef.remove(oldName);
    gamesBeingPlayedRef.put(newName, g);
  }

  protected void removeGame(String name) {
    PICAIUGame g = gamesBeingPlayedRef.get(name);
    if (g == null) {
      return;
    }
    sendDataToAll("RMVGM#" + g.getFormattedGameName());
    gamesBeingPlayedRef.remove(g.getFormattedGameName());
    gamesBeingPlayed.remove(g);
  }

  protected void removeGame(PICAIUGame g) {
    if (g == null) {
      return;
    }
    sendDataToAll("RMVGM#" + g.getFormattedGameName());
    gamesBeingPlayedRef.remove(g.getFormattedGameName());
    gamesBeingPlayed.remove(g);
  }

  protected Iterable<PICAIUGame> getGames() {
    return gamesBeingPlayed;
  }

  protected void clearStartedGames(PICAIUServerPersona persona) {
    if (persona == null) {
      return;
    }
    persona.removeGame();
  }

}
